import { Options, resolveOptions } from './options'
import { stringifyTyped, stringifyValue } from './stringify'
import * as fs from 'fs-extra'
import { parse } from './parser'

export function generateDts(options?: Options): void
export function generateDts(obj: Record<string, any>, options: Options): void

export function generateDts(objOrOptions?: any, maybeOptions?: Options) {
  let obj: any
  let options: any

  if (typeof maybeOptions === 'object' && maybeOptions) {
    obj = typeof objOrOptions === 'object' && objOrOptions ? objOrOptions : parse(objOrOptions)
    options = maybeOptions
  } else {
    obj = parse(objOrOptions)
    options = objOrOptions
  }

  const { dtsTarget, outputDts: dts } = resolveOptions(options)
  if (!dts) return

  const dtsObject = Object.fromEntries(
    Object.entries(obj)
      .map(([key, value]) => {
        value = stringifyTyped(value)
        if (!value) return null as any
        return [key, value]
      })
      .filter((v) => v)
  )

  const interfaceStr = `
export interface DotEnv {
${Object.entries(dtsObject)
  .map(([key, value]) => `${key}: ${value};`)
  .filter((v) => v)
  .join('\n')}
}
  `

  let globalstr = ''

  for (const target of new Set(dtsTarget)) {
    switch (target) {
      case 'global':
        globalstr += `  ${Object.entries(dtsObject)
          .map(([key]) => `const ${key}: DotEnv['${key}'];`)
          .join('\n')}\n`
        break
      case 'process.env':
        globalstr += `
  namespace NodeJS {
    interface ProcessEnv extends DotEnv { }
  }\n`
        break
      case 'import.meta.env':
        globalstr += `
  interface ImportMeta {
    env: DotEnv
  }\n`
        break
    }
  }

  const str = `${interfaceStr}\nglobal { \n${globalstr}\n}\n`

  try {
    if (fs.readFileSync(dts).toString() === str) return
  } catch {}
  fs.outputFileSync(dts, str)
}

function _generateDefines(key: string, val: any, defines: Record<string, any>) {
  if (typeof val === 'symbol') return
  defines[key] = stringifyValue(val)
  if (typeof val === 'object' && val) {
    for (const [key$1, val$1] of Object.entries(val)) {
      if (+key$1 === +key$1) {
        _generateDefines(`${key}[${key$1}]`, val$1, defines)
      }
      _generateDefines(`${key}['${key$1}']`, val$1, defines)
      _generateDefines(`${key}["${key$1}"]`, val$1, defines)
      if (/^[a-zA-Z$_]/.test(key$1)) {
        _generateDefines(`${key}.${key$1}`, val$1, defines)
      }
    }
    if (Array.isArray(val)) {
      _generateDefines(`${key}.length`, val.length, defines)
    }
  }
}

export function generateDefines(obj: Record<string, any>) {
  const defines = {} as Record<string, any>
  for (const [key, val] of Object.entries(obj)) {
    _generateDefines(key, val, defines)
  }
  return defines
}
